import { PartBase, PartHost, PartSlaver } from "../parts/PartBase";
import {EventEmitter} from '../../base/events';
import { Stud } from "../Stud";
import { Usage } from "../StudUsage";
import { Wire } from "./Wire";

export class Bus extends EventEmitter {
    
    public wires: Wire[] = []

    private static _id: number = 0 ;
    private id = 0

    public get usage() {
        let hostStud = this.findHostStud()
        return hostStud? hostStud.usage: Usage.none
    }

    constructor() {
        super()
        this.id = Bus._id ++
    }

    public findHostStud(): Stud|null {
        let provider: Stud|null = null
        this.allStuds(stud=>{
            if(!(stud.part instanceof PartHost))
                return
            if(!provider) {
                provider = stud
                return false
            }
        })
        return provider
    }



    addWire(wire: Wire) {

        let provider = this.findHostStud()

        this.wires.push(wire)
        // this.emit("bus.join", wire)

        if( !wire.from?.bus ){
            wire.from.bus = this
            this.emitStudChange("bus.join", wire.from)
        }
        if( !wire.to.bus ){
            wire.to.bus = this
            this.emitStudChange("bus.join", wire.to)
        }

        wire.bus = this

        let newProvider = this.findHostStud()
        if(newProvider!=provider) {
            this.emitMasterStudChange(newProvider, provider)
        }
    }
    removeWire(wire: Wire): Bus | undefined {

        let allSlavers: any = []
        this.allParts((part:PartBase, stud:Stud)=>{ if(part instanceof PartSlaver) allSlavers.push([part,stud]) })
        let provider = this.findHostStud()


        function disconnectStudFromWire(w: Wire, stud: Stud) {
            stud.wires.removeFirst(w)
            if(stud.wires.length==0 && stud.bus) {
                stud.bus.emitStudChange("bus.leave", stud)
                stud.bus = undefined
            }
        }
        
        disconnectStudFromWire(wire, wire.from)
        disconnectStudFromWire(wire, wire.to)

        this.wires.removeFirst(wire)

        wire.bus = undefined

        // wire的两端都还存在其他 wire, 可能需要分离bus
        let separatedBus = undefined
        if(wire.from.wires.length && wire.to.wires.length) {
            
            // 检查循环连线，并统计断开的wire
            var separateWires: Wire[] = []
            var step = (stud: Stud): boolean => {
                for(var w of stud.wires) {
                    if(w==wire) debugger
                    if( separateWires.indexOf(w)>-1 ){
                        continue
                    }
                    separateWires.push(w)
                    if( w.to==wire.from || w.from==wire.from ) {
                        // 发现回环
                        // 回环连接的总线，即使断开一条 wire, 也不需要分离
                        return false
                    }

                    // 递归
                    if( !step(w.to==stud? w.from: w.to) ){
                        return false
                    }
                }
                return true
            }
            
            if( step(wire.to) ) {
                // 分离总线
                separatedBus = new Bus
                for(var w of separateWires) {
                    this.wires.removeFirst(w)
                        
                    w.from.bus && w.from.bus.emitStudChange("bus.leave", w.from)
                    w.from.bus = undefined
                    w.to.bus && w.to.bus.emitStudChange("bus.leave", w.to)
                    w.to.bus = undefined

                    separatedBus.addWire(w)
                }
            }
        }
        
        
        let newProvider = this.findHostStud()
        if(newProvider!=provider) {
            for(let [part, stud] of allSlavers) {
                part.onStudProviderChanged(stud, newProvider, provider)
            }
        }
        return separatedBus
    }

    merge(other: Bus) {
        var wire
        while(wire=other.wires.pop()) {
            if( wire.from.bus ) {
                wire.from.bus = undefined
                other.emitStudChange("bus.leave", wire.from)
            }
            if( wire.to.bus ) {
                wire.to.bus = undefined
                other.emitStudChange("bus.leave", wire.to)
            }

            this.addWire(wire)
        }
        return this
    }

    emitStudChange(eventName: string, stud: Stud) {
        this.emit(eventName, stud, this)
        stud.emit(eventName, stud, this)
    }

    emitMasterStudChange(now:Stud|null, old:Stud|null) {
        this.allParts((part:PartBase, stud:Stud)=>{
            if(part instanceof PartSlaver) {
                part.onStudProviderChanged(stud, now, old) ;
            }
        })
    }
    
    // 返回/遍历 总线上所有 stud 的数组
    // callback 如果返回 false， 则停止迭代
    allStuds(callback?: (stud:Stud)=>void|false, excludes?: Stud[]): Stud[] | null {
        try{
            return this.wires
                .reduce((arr: Stud[], wire:Wire)=>{
                    if(!arr.includes(wire.from) && (!excludes || !excludes.includes(wire.from))){
                        arr.push(wire.from)
                        if(callback) {
                            if(callback(wire.from)==false){
                                throw {__break:true}
                            }
                        }
                    }
                    if(!arr.includes(wire.to) && (!excludes || !excludes.includes(wire.to)))
                        arr.push(wire.to)
                        if(callback) {
                            if(callback(wire.to)==false){
                                throw {__break:true}
                            }
                        }
                    return arr
                }, [])
        }catch(e:any){
            if(e.__break) {
                return null
            }
            else 
                throw e
        }
    }

    // 返回/遍历 总线上所有的零件
    allParts(callback: (part:PartBase, stud:Stud)=>void): PartBase[] {
        let items : PartBase[] = []
        this.allStuds((stud:Stud)=>{
            if( !items.includes(stud.part) ) {
                items.push(stud.part)
                if(callback) 
                    callback(stud.part, stud)
            }
        })
        return items
    }

    // 从 bus 上找 bus所属的 cable
    // findCable(): Cable|undefined {
    //     let cable = undefined
    //     this.allStuds((stud:Stud)=>{
    //         if(stud.serial) {
    //             cable = stud.serial
    //             return false
    //         }
    //     })
    //     return cable
    // }
}